package com.leyou.search.service;

import com.alibaba.nacos.client.utils.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.jayway.jsonpath.internal.function.numeric.AbstractAggregation;
import com.leyou.common.exception.pojo.ExceptionEnum;
import com.leyou.common.exception.pojo.LyException;
import com.leyou.common.pojo.PageResult;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.SearchRepository;
import com.leyou.search.utils.HighlightUtils;
import com.ly.plus.dto.SpuDTO;
import com.ly.plus.entity.*;
import org.apache.lucene.index.Term;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.filter.Filters;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Created with IntelliJ IDEA.
 * User: ycs
 * Date: 2020/8/21
 * Time: 20:19
 * Description: No Description
 * 索引库搜索业务层
 * @author ycs
 */
@Service
public class SearchService {
    /**
     * 简单操作
     */
    @Autowired
    private SearchRepository SearchRepository;
    @Autowired
    /**
     * 复杂操作
     */
    private ElasticsearchTemplate elasticsearchTemplate;
    @Autowired
    private ItemClient itemClient;

    public Goods buildGoods(SpuDTO spuDTO) {
        //设置已有的属性
        Goods goods = new Goods();
        goods.setId(spuDTO.getId());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setSpuName(spuDTO.getName());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        //未有的属性
        //获取skus
        List<Sku> skus = itemClient.findSkuBySpuId(spuDTO.getId());
        //因为sku中属性太多不需要存储这么多所以创建一个新的集合存储
        List<Map<String, Object>> skuList = new ArrayList<>();
        skus.forEach(sku -> {
            HashMap<String, Object> skuMap = new HashMap<>();
            //就需要4个属性
            skuMap.put("id", sku.getId());//id
            skuMap.put("title", sku.getTitle().substring(spuDTO.getName().length()));//标题
            skuMap.put("price", sku.getPrice());//价格
            skuMap.put("images", sku.getImages());//图片
            skuList.add(skuMap);
        });
        goods.setSkus(JsonUtils.toString(skuList));
        //拼接all字段
        //分类名与品牌名与sku标题
        String all = spuDTO.getCategoryName() + spuDTO.getBrandName()
                + skus.stream().map(Sku::getTitle).collect(Collectors.joining());
        goods.setAll(all);
        //获取价格集合
        Set<Long> priceSet = skus.stream().map(Sku::getPrice).collect(Collectors.toSet());
        goods.setPrice(priceSet);
        //根据分类id获取所有参与搜索的规格参数
        List<SpecParam> specParams = itemClient.findSpecParams(null, spuDTO.getCid3(), true);
        //获取spuDetail因为spuDetail对象中有规格参数的值
        SpuDetail spuDetail = itemClient.findSpuDetailById(spuDTO.getId());
        //获取通用规格参数的值
        //{"1":"华为","2":"G9青春版（全网通版）","3":2016,"5":143,"6":"陶瓷","7":"Android","8":"骁龙（Snapdragon)","9":"骁龙617（msm8952）","10":"八核","11":1.5,"14":5.2,"15":"1920*1080(FHD)","16":800,"17":1300,"18":3000}
         //格式数map结构一个key对应一个值
        String genericSpecStr = spuDetail.getGenericSpec();
        //转出map
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(genericSpecStr, Long.class, Object.class);
        //获取特有的规格参数值
        //map结构,一个key对应多个值
        String specialSpecStr = spuDetail.getSpecialSpec();
        //转出map
        Map<Long, List<Object>> specialSpecMap = JsonUtils.nativeRead(specialSpecStr, new TypeReference<Map<Long, List<Object>>>() {
        });
        //创建动态SpecsMap过滤条件
        HashMap<String, Object> specsMap = new HashMap<>();
        specParams.forEach(specParam -> {
            //设置key
            String key = specParam.getName();
            Object value = null;
            //判断通用规格参数或者特有规格参数
            if (specParam.getGeneric()) {
                //从通用规格参数值中获取
                value = genericSpecMap.get(specParam.getId());
            } else {
                //从特殊规格参数值中获取
                value = specialSpecMap.get(specParam.getId());
            }
            //判断是否是数值类型
            if (specParam.getNumeric()) {
                //转换成区间
                value = chooseSegment(value, specParam);
            }

            specsMap.put(key, value);
        });
        goods.setSpecs(specsMap);

        return goods;
    }

    /**
     * 条件搜索查询
     *
     * @param searchRequest
     * @return
     */
    public Map loadSearchPage(SearchRequest searchRequest) {
        //获取分页对象
        PageResult<GoodsDTO> pageResult = itemPageQuery(searchRequest);
        //获取搜索过滤结果集
        Map<String, List<?>> filterConditionsMap = aggQuery(searchRequest);
        //封装返回结果
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("filterConditions", filterConditionsMap);
        resultMap.put("itemsList", pageResult.getItems());
        resultMap.put("totalCount", pageResult.getTotal());
        resultMap.put("totalPages", pageResult.getTotalPage());
        return resultMap;
    }

    private Map<String, List<?>> aggQuery(SearchRequest request) {
        LinkedHashMap<String, List<?>> resultHashMap = new LinkedHashMap<>();
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //封装分页添加
        nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
        //封装需要字段
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //设置搜索条件
        nativeSearchQueryBuilder.withQuery(createQueryBuilder(request));
        //设置聚合条件
        String categoryAgg = "categoryAgg";
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        String brandAgg = "brandAgg";
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        AggregatedPage<Goods> goodsAggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
        //获取结果集
        Aggregations aggregations = goodsAggregatedPage.getAggregations();
        //处理分类信息
        Terms categoryTerms = aggregations.get(categoryAgg);
        //返回分类id集合(获取规格参数需要)
        List<Long> cids = Handlercategory(resultHashMap, categoryTerms);
        //处理品牌信息
        Terms brandTerms = aggregations.get(brandAgg);
        HandlerBrand(resultHashMap, brandTerms);
        //处理动态规格参数
        handlerFilterParamsAgg(resultHashMap, createQueryBuilder(request), cids);
        return resultHashMap;
    }

    /**
     * 处理动态规格参数(聚合动态规格参数)
     */
    private void handlerFilterParamsAgg(LinkedHashMap<String, List<?>> resultHashMap, QueryBuilder queryBuilder, List<Long> cids) {
        cids.forEach(cid -> {
            //获取封装条件对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装条件
            nativeSearchQueryBuilder.withQuery(queryBuilder);
            //封装分页条件
            nativeSearchQueryBuilder.withPageable(PageRequest.of(0,1));
            //封装查询字段
            nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""},null));
            //根据cid查询规格参数
            List<SpecParam> specParams = itemClient.findSpecParams(null, cid, true);
            specParams.forEach(specParam -> {
                //指定聚合的名称
                String specAgg = specParam.getName();
                //得到当前聚合的规格参数的域名称
                String field = "specs."+specAgg+".keyword";
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(specAgg).field(field));
            });
            AggregatedPage<Goods> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
            //获取所有聚合结果
            Aggregations aggregations = aggregatedPage.getAggregations();
            //遍历获取聚合结果
            specParams.forEach(specParam -> {
                String aggspec = specParam.getName();
                Terms aggregation = aggregations.get(aggspec);
                List<String> specList = aggregation.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
                resultHashMap.put(aggspec,specList);
            });
        });
    }

    /**
     * 处理品牌信息
     * @param resultHashMap
     * @param brandTerms
     */
    private void HandlerBrand(LinkedHashMap<String, List<?>> resultHashMap, Terms brandTerms) {
        //获取品牌id
        List<Long> bids = brandTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        //根据品牌id获取品牌集合
        List<Brand> brands = itemClient.findBrandByIds(bids);
        //存入集合
        resultHashMap.put("品牌", brands);

    }

    /**
     * 处理分类信息
     * @param resultHashMap
     * @param categoryTerms
     * @return
     */
    private List<Long> Handlercategory(LinkedHashMap<String, List<?>> resultHashMap, Terms categoryTerms) {
        //获取分类id集合
        List<Long> cids = categoryTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        //获取分类集合
        List<Category> categorys = itemClient.findCategorysByIds(cids);
        resultHashMap.put("分类", categorys);
        return cids;
    }

    private PageResult<GoodsDTO> itemPageQuery(SearchRequest request) {
        //获取封装条件对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //封装分页添加
        nativeSearchQueryBuilder.withPageable(PageRequest.of(request.getPage() - 1, request.getSize()));
        //封装查询条件
        nativeSearchQueryBuilder.withQuery(createQueryBuilder(request));
        //封装需要字段
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "spuName", "subTitle", "skus"}, null));
        //设置高亮字段
        HighlightUtils.highlightField(nativeSearchQueryBuilder, "spuName");
        NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
        AggregatedPage<Goods> goodsAggregatedPage = elasticsearchTemplate.queryForPage(
                nativeSearchQuery,
                Goods.class,
                HighlightUtils.highlightBody(Goods.class, "spuName"));
        //获取结果集
        List<Goods> goodsList = goodsAggregatedPage.getContent();
        if (CollectionUtils.isEmpty(goodsList)) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);
        PageResult pageResult = new PageResult(
                goodsAggregatedPage.getTotalElements(),
                Long.valueOf(goodsAggregatedPage.getTotalPages()),
                goodsDTOS
        );
        return pageResult;
    }

    /**
     * 封装查询条件方法
     * @param request
     * @return
     */
    private QueryBuilder createQueryBuilder(SearchRequest request) {
        //获取封装过滤条件对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //设置搜索条件
        boolQuery.must(QueryBuilders.multiMatchQuery(request.getKey(), "spuName", "all").operator(Operator.AND));
        //获取过滤条件
        Map<String, Object> filterParamsObj = request.getFilterParamsObj();
        if (CollectionUtils.isNotEmpty(filterParamsObj)){
            //遍历过滤条件
            filterParamsObj.forEach((key, value)->{
                if (StringUtils.equals(key,"分类")){
                    key="categoryId";
                }else if (!StringUtils.equals(key,"brandId")){
                    key = "specs."+key+".keyword";
                }
                boolQuery.filter(QueryBuilders.termQuery(key,value));
            });
        }

        return boolQuery;
    }


    /**
     * 转换成区间方法
     * @param value
     * @param p
     * @return
     */
    private String chooseSegment(Object value, SpecParam p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 页码变化查询
     * @param searchRequest
     * @return
     */
    public List<GoodsDTO> pageChange(SearchRequest searchRequest) {
        PageResult<GoodsDTO> pageResult = itemPageQuery(searchRequest);
        return pageResult.getItems();
    }
    /**添加索引库*/
    public void addItem(Long spuId) {
        SpuDTO spuDTO = itemClient.findSpuById(spuId);
        Goods goods = buildGoods(spuDTO);
        SearchRepository.save(goods);
    }

    /**
     * 删除索引库
     * @param spuId
     */
    public void delItem(Long spuId) {
       SearchRepository.deleteById(spuId);
    }
}
